環境やセキュリティポリシーによってはBTS(Bug Tracking System)などのデータベースに直接アクセスできずCSV形式ファイルなどでデータをインポートしなければならない場合があります。このような場合、一つのファイルで全てのデータを入手できずに複数のファイルになる場合があります。そこで本ページではRedmineのチケットデータを利用し複数のファイルを一括処理してデータフレームに読み込む方法を紹介します。
ただし、複数のファイルを一括処理する場合は全てのファイルで項目(列)が同一である必要があります。個々のファイルで項目(列)が異なる場合は、一括読み込みは可能ですが、ファイルの結合処理等は本ページの方法ではできませんので注意してください。
なお、本ページではR version 3.4.4 (2018-03-15)の標準パッケージ以外に以下の追加パッケージを用いています。
| Package | Version | Description |
|---|---|---|
| tidyverse | 1.2.1 | Easily Install and Load the ‘Tidyverse’ |
また、本ページでは以下のデータセットを用いています。
| Dataset | Package | Version | Description |
|---|---|---|---|
| redmine | N/A | N/A | Redmine Issues |
RedmineはGPL v2ライセンスの下で提供されているオープンソースのプロジェクト管理ソフトウェアです。BTS機能を備えておりRedmine自身のチケットをRedmineを用いて公開しています。ただし、一度に50レコードまでしかダウンロードできないため長期間に渡るレコードを入手しようとすると必然的に複数のCSVファイルをダウンロードすることになります。
複数のファイルのインポートを説明する前にRedmineで標準的に用意されている項目を簡単に説明しておきます。
これはファイルの読み込みに利用するreadr::read_csv関数が読み込んだ最初の100行のデータを元に列(カラム、変数)のデータ型を自動的に判別するために、あまり使わていない(データが入力されない)カラムの場合、ファイル毎にデータ型の判定結果が異なってしまう可能性があるため事前にカラム(変数)タイプを指定しておく必要があるためです。
| 項目 | 概要 | データ型 |
|---|---|---|
| # | 識別番号(Primary Key) | 整数型 |
| プロジェクト | 属するプロジェクト | 文字型(因子型) |
| トラッカー | 大分類 | 文字型(因子型) |
| 親チケット | 親子関係を定義したい場合に用いる | 文字型 |
| ステータス | 対応状況 | 文字型(因子型) |
| 優先度 | 対応優先度 | 文字型(因子型) |
| 題名 | タイトル | 文字型 |
| 作成者 | 作成者 | 文字型(因子型) |
| 担当者 | 対応担当者 | 文字型(因子型) |
| 更新日 | 更新日時 | 日時型(POSIXct) |
| カテゴリ | 分類(任意に利用設定できる) | 文字型(因子型) |
| 対象バージョン | チケット対処したバージョン | 文字型 |
| 開始日 | 対応を開始した日 | 日付型 |
| 期日 | 対応予定期間 | 日付型 |
| 予定工数 | 対応予定工数 | 数値型 |
| 進捗率 | 対応の進捗率 | 数値型(%表記) |
| 作成日 | 作成日時 | 日時型(POSIXct) |
| 終了日 | 対応完了日時 | 日時型(POSIXct) |
| 関連するチケット | 関係するチケット番号 | 文字型 |
| Resolution | 解決結果(非標準) | 文字型(因子型) |
| Affected version | 影響のあるバージョン | 文字型 |
| 説明 | 詳細 | 文字型 |
このようにカラム(変数)タイプを事前に把握しておくことで読み込み後の結合時に絡む形式不一致のエラー発生を抑制することが可能になります。
前述の通り、Redmineからは一度に50レコードしかダウンロードできませんので、ダウンロードした複数のファイルを結合する必要があります。
複数のデータファイルを結合するには基本的に以下の手順にしたがいます。
ファイルやディレクトリを扱うためのlist.*関数群、file.*関数群が用意されています。
list.files(path = 'ファイルリストを取得したいパス',
pattern = '正規表現によるファイル名の指定' # 省略可
full.names = TRUE)
Windows環境ではchoose.dir関数を用いて’ファイルリストを取得したいパス’をインタラクティブに指定することも可能です。
"./data/" %>%
list.files(path = ., pattern = "(issues_)", full.names = TRUE) %>%
tibble::as_data_frame()
この場合は80個のファイルがあることが分かります。
Redmineチケットのように自由記述欄があるようなデータはCSV形式で書きだしていてもExcelで読みこんだ際に正しく処理されない場合があります。このような場合は標準のread.csv関数でなくreadrパッケージを用いる方が正しく読みこめる可能性が高くなります。
readr::read_csv関数は読み込んだ最初の100行のデータを元に列(カラム、変数)のデータ型を自動的に判別します。ただし、稀にしか使われていない列がある場合、読み込んだファイルごとに列のデータ型が異なってしまう場合があります。このような場合は前述のようにカラムタイプを明示的に指定しておきます。
ファイルの結合処理を考慮してlapply関数でファイルのリストから一括で読み込み処理します。
lapply(X = 'ベクトル型のファイルリスト',
FUN = readr::read_csv,
locale = readr::locale(encoding = "cp932"),
col_types = 'カラムタイプ')
"./data/" %>%
list.files(path = ., pattern = "(issues_)", full.names = TRUE) %>%
lapply(X = ., FUN = readr::read_csv,
locale = readr::locale(encoding = "cp932"), col_types = col_type)
実行結果が冗長なので省略します。
行方向の結合にはbind系の関数を使います。出力分だけ行方向に結合する関数(rbindやdplyr::bind_rows)関数を記述するのは手間がかかりますのでdo.call関数を用いて一括で処理します。
do.call(what = dplyr::bind_rows, args = 'リスト形式の引数')
"./data/" %>%
list.files(path = ., pattern = "(issues_)", full.names = TRUE) %>%
lapply(X = ., FUN = readr::read_csv,
locale = readr::locale(encoding = "cp932"), col_types = col_type) %>%
do.call(what = dplyr::bind_rows, args = .)
lapply関数とdo.call関数の処理はpurrr::map_df関数を用いることでよりシンプルに記述することができます。purrrパッケージの詳細については()[../tidy/purrr.html]をご覧ください。
"./data/" %>%
list.files(path = ., pattern = "(issues_)", full.names = TRUE) %>%
purrr::map_df(.x = ., .f = readr::read_csv,
locale = readr::locale(encoding = "cp932"), col_types = col_type)
複数のCSVファイルを結合処理しましたのでレコード(行)が重複している可能性があります。このような場合はdplyr::distinct関数を用いて重複行を削除しておきます。
dplyr::distinct(.data = 'データ')
"./data/" %>%
list.files(path = ., pattern = "(issues_)", full.names = TRUE) %>%
lapply(X = ., FUN = readr::read_csv,
locale = readr::locale(encoding = "cp932"), col_types = col_type) %>%
do.call(what = dplyr::bind_rows, args = .) %>%
dplyr::distinct()
カラム(変数)タイプは前述の表にしたがってリスト型で指定します。本来であれば因子型で指定すべき項目は文字型として指定します。因子型は指定時に水準も含めて指定する必要があるため水準を把握していない(できない)場合には読み込めなくなります。
col_type <- list(readr::col_integer(), readr::col_character(),
readr::col_character(), readr::col_character(),
readr::col_character(), readr::col_character(),
readr::col_character(), readr::col_character(),
readr::col_character(), readr::col_datetime(format = ""),
readr::col_character(), readr::col_character(),
readr::col_date(), readr::col_date(), readr::col_double(),
readr::col_double(), readr::col_datetime(format = ""),
readr::col_datetime(format = ""), readr::col_character(),
readr::col_character(), readr::col_character(),
readr::col_character())
変数名が日本語のままだと何かと扱い難いので変更しておきます。また、チケットオープン、クローズは時間まで必要ないので年月日データに変換しておきます。
(x <- tmp %>%
dplyr::select(id = `#`, tracker = `トラッカー`, status = `ステータス`,
priority = `優先度`, category = `カテゴリ`,
version = `対象バージョン`, affected = `Affected version`,
open = `作成日`, close = `終了日`) %>%
dplyr::mutate(open = lubridate::date(open), close = lubridate::date(close)))
[Sampo Suzuki][CCI] [CC BY-NC-SA 4.0 ][CC], Sampo Suzuki [2018-08-28 11:06(JST)]